package cn.xiaopengstack.infrastructure.persistent.repository;

import cn.bugstack.middleware.db.router.strategy.IDBRouterStrategy;
import cn.xiaopengstack.domain.credit.even.CreateAdjustSuccessEvent;
import cn.xiaopengstack.domain.task.model.TaskEntity;
import cn.xiaopengstack.domain.xxx.model.valobj.TradeTypeVO;
import cn.xiaopengstack.domain.credit.model.aggregate.TradeCreditAggregate;
import cn.xiaopengstack.domain.credit.model.entity.CreditAccountEntity;
import cn.xiaopengstack.domain.credit.model.entity.CreditOrderEntity;
import cn.xiaopengstack.domain.credit.repository.ICreditRepository;
import cn.xiaopengstack.infrastructure.event.EventPublisher;
import cn.xiaopengstack.infrastructure.persistent.dao.ITaskDao;
import cn.xiaopengstack.infrastructure.persistent.dao.IUserCreditAccountDao;
import cn.xiaopengstack.infrastructure.persistent.dao.IUserCreditOrderDao;
import cn.xiaopengstack.infrastructure.persistent.po.Task;
import cn.xiaopengstack.infrastructure.persistent.po.UserCreditAccount;
import cn.xiaopengstack.infrastructure.persistent.po.UserCreditOrder;
import cn.xiaopengstack.infrastructure.persistent.redis.IRedisService;
import cn.xiaopengstack.types.common.Constants;
import cn.xiaopengstack.types.enums.ResponseCode;
import cn.xiaopengstack.types.exception.AppException;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.support.TransactionTemplate;

import java.math.BigDecimal;
import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * @author jiangyangang
 */
@Repository
@Slf4j
public class CreditRepository implements ICreditRepository {

    private final IUserCreditAccountDao userCreditAccountDao;

    private final IUserCreditOrderDao userCreditOrderDao;

    private final IDBRouterStrategy dbRoute;

    private final IRedisService redisService;

    private final TransactionTemplate transactionTemplate;

    private final ITaskDao taskDao;

    private final EventPublisher eventPublisher;

    public CreditRepository(IUserCreditAccountDao userCreditAccountDao, IUserCreditOrderDao userCreditOrderDao, IDBRouterStrategy dbRoute, IRedisService redisService, TransactionTemplate transactionTemplate, ITaskDao taskDao, EventPublisher eventPublisher) {
        this.userCreditAccountDao = userCreditAccountDao;
        this.userCreditOrderDao = userCreditOrderDao;
        this.dbRoute = dbRoute;
        this.redisService = redisService;
        this.transactionTemplate = transactionTemplate;
        this.taskDao = taskDao;
        this.eventPublisher = eventPublisher;
    }

    @Override
    public void saveUserCreditTradeOrder(TradeCreditAggregate aggregate) {
        String userId = aggregate.getUserId();
        CreditAccountEntity creditAccountEntity = aggregate.getCreditAccountEntity();
        CreditOrderEntity creditOrderEntity = aggregate.getCreditOrderEntity();
        TaskEntity taskEntity = aggregate.getTaskEntity();
        CreateAdjustSuccessEvent<CreateAdjustSuccessEvent.CreateAdjustSuccessEventMessage> event = aggregate.getEvent();

        Task task = new Task();
        task.setUserId(taskEntity.getUser().getUserId());
        task.setMessageId(taskEntity.getMessageId());
        task.setMessage(taskEntity.getMessage());
        task.setState(taskEntity.getState().getCode());
        task.setTopic(taskEntity.getTopic());
        task.setCreateTime(new Date());
        task.setUpdateTime(new Date());

        UserCreditOrder userCreditOrder = new UserCreditOrder();
        userCreditOrder.setOrderId(creditOrderEntity.getOrderId());
        userCreditOrder.setTradeName(creditOrderEntity.getTradeName());
        userCreditOrder.setTradeType(creditOrderEntity.getTradeType());
        userCreditOrder.setTradeAmount(creditOrderEntity.getTradeAmount());
        userCreditOrder.setOutBusinessNo(creditOrderEntity.getOutBusinessNo());
        userCreditOrder.setUserId(userId);
        userCreditOrder.setCreateTime(new Date());
        userCreditOrder.setUpdateTime(new Date());

        // 上锁
        RLock lock = redisService.getLock(String.format(Constants.RedisKey.CREDIT_ORDER_SAVE_LOCK_KEY, userId));
        try{
            lock.lock(3, TimeUnit.SECONDS);

            dbRoute.doRouter(userId);
            transactionTemplate.execute(state -> {
                try {
                    userCreditAccountDao.updateCreditAmount(creditAccountEntity.getUserId(), creditOrderEntity.getTradeAmount());
                    userCreditOrderDao.insertSelective(userCreditOrder);
                    taskDao.insertSelective(task);
                    return 1;
                } catch (Exception e) {
                    log.error("保存积分订单失败", e);
                    state.setRollbackOnly();
                    throw new AppException(ResponseCode.CREDIT_SAVE_ORDER_ERROR.getCode(), ResponseCode.CREDIT_SAVE_ORDER_ERROR.getInfo());
                }
            });

            // 发送mq
            try {
                eventPublisher.publish(event);
                taskDao.updateTaskSendMessageCompleted(task);
            } catch (Exception e) {
                taskDao.updateTaskSendMessageFail(task);
                log.error("积分调额发送MQ失败, userId:{}, topic:{}, message:{}", task.getUserId(), task.getTopic(), task.getMessage());
            }
        }finally {
            dbRoute.clear();
            lock.unlock();
        }
    }

    @Override
    public CreditAccountEntity queryUserCreditAccount(String userId) {
        UserCreditAccount userCreditAccountQuery = new UserCreditAccount();
        userCreditAccountQuery.setUserId(userId);
        UserCreditAccount creditAccount = userCreditAccountDao.queryUserCreditAccount(userCreditAccountQuery);
        return CreditAccountEntity.builder().userId(userId).adjustAmount(creditAccount.getAvailableAmount()).build();
    }
}
